package com.simple.middle.test.redis.util;

import com.simple.middle.test.redis.enums.DistanceUnitEnum;
import com.simple.middle.test.redis.statistic.model.data.GeoData;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Metric;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.GeoOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;

/**
 * 项目: pagination
 *
 * 功能描述: geo的redis相关的工具类
 *
 * @author: WuChengXing
 *
 * @create: 2023-05-30 14:29
 **/
@Component
@Slf4j
public class GeoRedisUtils {

    @Resource
    private RedisTemplate redisTemplate;

    private GeoOperations<String, String> geoOperations = redisTemplate.opsForGeo();
    private ZSetOperations<String, String> zSetOperations = redisTemplate.opsForZSet();

    /**
     * 添加位置坐标
     *
     * @param key
     * @param subKey
     * @param longitude
     * @param latitude
     * @return
     */
    public Long geoAdd(String key, String subKey, Double longitude, Double latitude) {
        Point point = new Point(longitude, latitude);
        RedisGeoCommands.GeoLocation geoLocation = new RedisGeoCommands.GeoLocation(subKey, point);
        return geoOperations.add(key, geoLocation);
    }

    /**
     * 批量添加位置坐标
     *
     * @param key
     * @param geoLocations
     *
     * @return
     */
    public Long geoAdd(String key, List<GeoLocation> geoLocations) {
        if (isEmpty(geoLocations)) {
            log.error("添加失败，key信息：{}", key);
            return -1L;
        }
        AtomicReference<Long> res = new AtomicReference<>(0L);
        geoLocations.forEach(s -> {
            Long aLong = geoAdd(key, s.getSubKey(), s.getLongitude(), s.getLatitude());
            res.updateAndGet(v -> v + aLong);
        });
        return res.get();
    }

    /**
     * 删除GEO位置信息
     *
     * @param key
     * @param subKeys
     * @return
     */
    public Long geoDelete(String key, String... subKeys) {
        return zSetOperations.remove(key, subKeys);
    }

    /**
     * 获取GEO位置的hash信息
     *
     * @param key
     * @param subKeys
     * @return
     */
    public Map<String, String> geoHash(String key, String... subKeys) {
        Map<String, String> hash = new HashMap<>(subKeys.length);
        List<String> hashKey = geoOperations.hash(key, subKeys);
        for (int i = 0; i < subKeys.length; i++) {
            hash.put(subKeys[i], hashKey.get(i));
        }
        return hash;
    }

    /**
     * 获取对应key的位置信息
     *
     * @param key
     * @param subKeys
     * @return
     */
    public Map<String, Point> geoPosition(String key, String... subKeys) {
        Map<String, Point> hash = new HashMap<>(subKeys.length);
        List<Point> position = geoOperations.position(key, subKeys);
        for (int i = 0; i < subKeys.length; i++) {
            hash.put(subKeys[i], position.get(i));
        }
        return hash;
    }

    /**
     * 获取两个用户的距离
     *
     * @param key
     * @param subKey1
     * @param subKey2
     * @param unit
     * @return
     */
    public GeoData geoDistance(String key, String subKey1, String subKey2, String unit) {
        Distance distance = geoOperations.distance(key, subKey1, subKey2, change(unit));
        return GeoData.builder()
                .distNum(String.valueOf(distance.getValue()))
                .distUnit(distance.getUnit())
                .build();
    }


    private Metric change(String unit) {
        DistanceUnitEnum unitEnum = DistanceUnitEnum.getByUnit(unit);
        switch (unitEnum) {
            case KILOMETERS:
                return RedisGeoCommands.DistanceUnit.KILOMETERS;
            case MILES:
                return RedisGeoCommands.DistanceUnit.MILES;
            case FEET:
                return RedisGeoCommands.DistanceUnit.FEET;
            default:
                return RedisGeoCommands.DistanceUnit.METERS;
        }
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    static class GeoLocation {

        private String subKey;

        private Double longitude;

        private Double latitude;
    }

    private <T> Boolean isEmpty(Collection<T> collection) {
        return Objects.isNull(collection) || collection.isEmpty();
    }
}
